package org.luxor.cloud.authentication.config;

import org.luxor.commons.security.component.Oauth2WebResponseExceptionTranslator;
import org.luxor.commons.security.config.WebSecurityProperties;
import org.luxor.commons.security.constant.SecurityConstants;
import org.luxor.commons.security.entity.UserSubject;
import org.luxor.commons.security.utils.SecurityUtils;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;

/**
 * 授权服务器配置
 *
 * @author Mr.Yan  @date: 2020/9/8
 */
@Order(87)
@Configuration
@EnableAuthorizationServer
@EnableConfigurationProperties(WebSecurityProperties.class)
public class AuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter {

    @Resource
    private ClientDetailsService clientDetailsService;
    @Resource
    private UserDetailsService userDetailsService;
    @Resource
    private AuthenticationManager authenticationManager;

    private RedisTokenStore tokenStore;

    @Bean
    @Primary
    public RedisTokenStore tokenStore(RedisConnectionFactory connectionFactory) {
        tokenStore = new RedisTokenStore(connectionFactory);
        return tokenStore;
    }

    /**
     * 定义客户端详细信息
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(clientDetailsService);
    }

    /**
     * 定义令牌端点上的安全约束
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {
        security.allowFormAuthenticationForClients().tokenKeyAccess("permitAll()").checkTokenAccess("permitAll()");
    }

    /**
     * 定义授权和令牌端点以及令牌服务(获取code、获取accessToken、token存储服务)
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST).tokenStore(tokenStore)
                .tokenEnhancer(tokenEnhancer()).userDetailsService(userDetailsService)
                .authenticationManager(authenticationManager)
                .pathMapping("/oauth/confirm_access", "/confirm_access")
                .pathMapping("/oauth/error", "/error")
                .exceptionTranslator(new Oauth2WebResponseExceptionTranslator());
    }

    @Bean
    public TokenEnhancer tokenEnhancer() {
        return (accessToken, authentication) -> {
            final Map<String, Object> additionalInfo = new HashMap<>(5);

            Authentication userAuthentication = authentication.getUserAuthentication();
            if (userAuthentication == null) {
                additionalInfo.put(SecurityConstants.TENANT_ID, SecurityUtils.ANONYMUS.getTenantId());
                additionalInfo.put(SecurityConstants.USER_ID, SecurityUtils.ANONYMUS.getUserId());
                additionalInfo.put(SecurityConstants.USERNAME, authentication.getOAuth2Request().getClientId());
                additionalInfo.put(SecurityConstants.REALNAME, authentication.getOAuth2Request().getClientId());
                additionalInfo.put(SecurityConstants.DEPT_ID, SecurityUtils.ANONYMUS.getDeptId());
                additionalInfo.put(SecurityConstants.ENABLED, SecurityUtils.ANONYMUS.isEnabled());
                additionalInfo.put(SecurityConstants.ACCOUNT_NON_LOCKED, SecurityUtils.ANONYMUS.isAccountNonLocked());

            } else {
                UserSubject userSubject = (UserSubject) userAuthentication.getPrincipal();
                additionalInfo.put(SecurityConstants.TENANT_ID, userSubject.getTenantId());
                additionalInfo.put(SecurityConstants.USER_ID, userSubject.getUserId());
                additionalInfo.put(SecurityConstants.USERNAME, userSubject.getUsername());
                additionalInfo.put(SecurityConstants.REALNAME, userSubject.getRealName());
                additionalInfo.put(SecurityConstants.DEPT_ID, userSubject.getDeptId());
                additionalInfo.put(SecurityConstants.ENABLED, userSubject.getDeptId());
                additionalInfo.put(SecurityConstants.ACCOUNT_NON_LOCKED, userSubject.isAccountNonLocked());
            }

            ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
            return accessToken;
        };
    }

}
